package com.syf.controller;

import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.syf.domain.SysPrivilege;
import com.syf.model.R;
import com.syf.service.SysPrivilegeService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;

/**
 * 1.权限管理的控制器
 *      1 查询 admin/privileges(接口路径)
 *      2 修改 admin/privileges
 *      3 新增 admin/privileges
 *      4 删除 admin/privileges/delete
 *
 *      当多个方法的URL相同时，使用请求方式进行区分:查询@GetMapper、新增@PostMapper、修改@PatchMapper
 *
 * 2.权限管理的权限：
 *      查询：sys_privilege_query
 *      修改：sys_privilege_update
 *      新增：sys_privilege_create
 *      删除：sys_privilege_delete
 *
 * 3.简单的单表CURD只需写controller层即可
 *
 * 4.Spring Security中可以通过表达式控制方法权限：【根据token中包含的角色、权限进行控制】
 *       Spring Security中定义了四个支持使用表达式的注解，分别是@PreAuthorize、@PostAuthorize、@PreFilter和@PostFilter。
 *       其中前两者可以用来在方法调用前或者调用后进行权限检查，后两者可以用来对集合类型的参数或者返回值进行过滤。
 *       要使它们的定义能够对我们的方法的调用产生影响我们需要设置global-method-security元素的pre-post-annotations=”enabled”，默认为disabled:
 *          <security:global-method-security pre-post-annotations="disabled"/>
 *      1.@PreAuthorize:
 *          可以用来控制一个方法是否能够被调用【该用户必须要具备相应的权限才可以进行相应的操作】
 *          eg:
 *              1)@PreAuthorize("hasAuthority('sys_privilege_query')")  //该用户必须具备'sys_privilege_query'权限才可以进行查询操作
 *              2) @PreAuthorize("#id<10")
 *                 public User find(int id) { ... }   //限制只能查询Id小于10的用户
 *              3)@PreAuthorize("#user.name.equals('abc')")
 *                public void add(User user) {...}    //限制只能新增用户名称为abc的用户
 *      2.@PostAuthorize:
 *          有时候可能你会想在方法调用完之后进行权限检查，这种情况比较少，但是如果你有的话
 *          需要注意的是@PostAuthorize是在方法调用完成后进行权限检查，它不能控制方法是否能被调用，只能在方法调用完成后检查权限决定是否要抛出AccessDeniedException
 *      3.@PreFilter和@PostFilter:
 *          可以对集合类型的参数或返回值进行过滤,Spring Security将移除使对应表达式的结果为false的元素。
 *          eg:
 *              1)@PostFilter("filterObject.id%2==0"）  //表示将对返回结果中id不为偶数的user进行移除。filterObject是使用@PreFilter和@PostFilter时的一个内置表达式，表示集合中的当前对象
 *              2)当@PreFilter标注的方法拥有多个集合类型的参数时，需要通过@PreFilter的filterTarget属性指定当前@PreFilter是针对哪个参数进行过滤的
 *              @PreFilter(filterTarget="ids", value="filterObject%2==0")
 *              public void delete(List<Integer> ids, List<String> usernames) {...}  //通过filterTarget指定了当前@PreFilter是用来过滤参数ids的
 *
 *      角色: "hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')"
 *      权限: "hasAuthority('sys_privilege_query')"
 *
 * 5.参数验证:
 *      @NotNull(message = "用户ID不能为空")  验证对象是否不为null, 无法查检长度为0的字符串(用在集合类上面)
 *      @NotBlank(message = "备注不能为空")   检查约束 (字符串) 是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格(用在String上面)
 *      @NotEmpty(message = "地址不能为空")   检查(集合)约束元素是否为NULL或者是EMPTY(用在基本类型上)
 *      @Min(value = 1, message = "id必须为正整数")
 *      @Size(min = 1, message = "至少要有一个属性")
 *      @Min(value = 1, message = "pid必须为正整数")  ...
 *      1.注意注解引用的包不一样
 *          import org.hibernate.validator.constraints.NotBlank;
 *          import org.hibernate.validator.constraints.NotEmpty;
 *          import javax.validation.constraints.NotNull;
 *      2.在检验Controller的入参是否符合规范时，使用@Validated或者@Valid在基本验证功能上没有太多区别。但是在分组、注解地方、嵌套验证等功能上两个有所不同：
 *        1)分组group:
 *              @Validated：提供了一个分组group功能，可以在入参验证时，根据不同的分组采用不同的验证机制
 *        2)注解地方:
 *              @Validated：可以用在类型、方法和方法参数上。但是不能用在成员属性（字段）上
 *              @Valid：可以用在方法、构造函数、方法参数和成员属性（字段）上
 *        3)嵌套验证:
 *              public class Item {
 *                  @NotNull(message = "id不能为空")
 *                  @Min(value = 1, message = "id必须为正整数")
 *                  private Long id;
 *
 *                  //@Valid    //嵌套验证必须用@Valid
 *                  @NotNull(message = "props不能为空")
 *                  @Size(min = 1, message = "至少要有一个属性")
 *                  private List<Prop> props;  //类集合【两个类连表】
 *              }
 *
 *              public class Prop {
 *                  @NotNull(message = "pid不能为空")
 *                  @Min(value = 1, message = "pid必须为正整数")
 *                  private Long pid;
 *
 *                  @NotNull(message = "vid不能为空")
 *                  @Min(value = 1, message = "vid必须为正整数")
 *                  private Long vid;
 *              }
 *
 *              @RestController
 *              public class ItemController {
 *                  @RequestMapping("/item/add")
 *                  public void addItem(@Validated Item item, BindingResult bindingResult) {
 *                      doSomething();
 *                  }
 *              }
 *              如果Item实体的props属性不额外加注释(@Valid)，只有@NotNull和@Size，无论入参采用@Validated还是@Valid验证，
 *              Spring Validation框架只会对Item的id和props做非空和数量验证，不会对props字段里的Prop实体进行字段验证，
 *              也就是@Validated和@Valid加在方法参数前，都不会自动对参数进行嵌套验证。
 *              也就是说如果传的List<Prop>中有Prop的pid为空或者是负数，入参验证不会检测出来。
 *
 *
 */
@RestController //返回值Json格式
@RequestMapping("/privileges") //Swagger中存储的URL以privileges开头
@Api(tags = "权限的管理") //Swagger API类说明
public class SysPrivilegeController {

    @Autowired
    private SysPrivilegeService sysPrivilegeService ;

    /**
     * 权限数据的分页查询
     * @param page
     * @return
     */
    @GetMapping
    @ApiOperation(value = "分页查询权限数据")  //Swagger API方法说明
    @CrossOrigin
    @ApiImplicitParams({
            @ApiImplicitParam(name = "current" ,value = "当前页") ,
            @ApiImplicitParam(name = "size" ,value = "每页显示的大小") ,
    })
    @PreAuthorize("hasAuthority('sys_privilege_query')") //@PreAuthorize可以用来控制一个方法是否能够被调用
    public R<Page<SysPrivilege>> findByPage(@ApiIgnore  Page<SysPrivilege> page){  //@ApiIgnore 可以用在类、方法上，方法参数中，用来屏蔽某些接口或参数，使其不在页面上显示

        // 查询时，我们将最近新增的、修改的数据优先展示-> 排序->lastUpdateTime
        page.addOrder(OrderItem.desc("last_update_time")) ;
        Page<SysPrivilege> sysPrivilegePage = sysPrivilegeService.page(page); //先排序，再查询
        return R.ok(sysPrivilegePage) ;
    }


    @PostMapping  //@RequestBody: 接收前端请求过来的Json格式参数
    @ApiOperation(value = "新增一个权限")
    @CrossOrigin
    @PreAuthorize("hasAuthority('sys_privilege_create')")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "sysPrivilege" ,value = "sysPrivilege 的json数据")
    })
    public R add(@RequestBody @Validated SysPrivilege sysPrivilege){  //对参数进行@NotNull验证，提供了一个分组group功能，可以在入参验证时，根据不同的分组采用不同的验证机制【分组group不常用】
//         新增时，我们需要给我们的新增对象填充一些属性
//        String userIdStr = SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString();
//        sysPrivilege.setCreateBy(Long.valueOf(userIdStr));
//        sysPrivilege.setCreated(new Date());
//        sysPrivilege.setLastUpdateTime(new Date());

        boolean save = sysPrivilegeService.save(sysPrivilege);
        if(save){
            return R.ok("新增成功") ;
        }
        return  R.fail("新增失败") ;
    }


    @PatchMapping
    @CrossOrigin
    @ApiOperation(value = "修改一个权限")
    @PreAuthorize("hasAuthority('sys_privilege_update')")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "sysPrivilege" ,value = "sysPrivilege 的json数据")
    })
    public R update(@RequestBody @Validated SysPrivilege sysPrivilege){
//        // 新增时，我们需要给我们的新增对象填充一些属性
//        String userIdStr = SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString();
//        sysPrivilege.setModifyBy(Long.valueOf(userIdStr));
//        sysPrivilege.setLastUpdateTime(new Date());

        boolean save = sysPrivilegeService.updateById(sysPrivilege);
        if(save){
            return R.ok("修改成功") ;
        }
        return  R.fail("修改失败") ;
    }
}

